package xalan.smartgwt.criteria;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

/**
 * Klasa odpowiedzialna za parsowania napisu reprezentującego kryterium na
 * obiekty typu Criteria.
 * 
 * Wykorzystywane przez Serwlet do konwersji parametrów przekazanych przez SmartGWT
 * na odpowiednie obiekty.
 *
 * @author Xalan
 */
public class CriteriaParser {
	private Map<String, Operator> valueToOperator;
	private Set<Operator> fieldOperators = new HashSet<Operator>();
	
	public CriteriaParser() {
		this.valueToOperator = new HashMap<String, Operator>();
		
		for(Operator op : Operator.values()) {
			this.valueToOperator.put(op.getValue(), op);
		}
		
		this.fieldOperators.add(Operator.EQUALS_FIELD);
		this.fieldOperators.add(Operator.GREATER_OR_EQUAL_FIELD);
		this.fieldOperators.add(Operator.GREATER_THAN_FIELD);
		this.fieldOperators.add(Operator.LESS_OR_EQUAL_FIELD);
		this.fieldOperators.add(Operator.LESS_THAN_FIELD);
		this.fieldOperators.add(Operator.NOT_EQUAL_FIELD);
	}

	/**
	 * Dokonuje transformacji napisu w formacie JSON do odpowiedniego obiektu Criteria
	 * 
	 * @see CriteriaParser#parse(JSONObject)
	 * @param filterString napis reprezentujący kryterium - jest w formacie JSON
	 * @return odpowiadający obiekt Criteria
	 */
	public Criteria parse(String filterString) {
		JSONObject jsonObject = JSONObject.fromObject(filterString);
		return parse(jsonObject);
	}
	
	/**
	 * Dokonuje transformacji obiektu JSON do odpowiedniego obiektu Criteria
	 * 
	 * @param jsonObject sparsowany obiekt JSON, który zostanie skonwertowany do obiektu Criteria
	 * @return odpowiadający obiekt Criteria
	 */
	public Criteria parse(JSONObject jsonObject) {
		if(jsonObject.containsKey("_constructor")) {
			return parseAdvancedCriteria(jsonObject);
		}
		else {
			return parseSimpleCriteria(jsonObject);
		}
	}

	private Criteria parseSimpleCriteria(JSONObject jsonObject) {
		SimpleCriteria criteria = null;
		
		Operator operator = getOperator(jsonObject);
		if(jsonObject.containsKey("value")) {
			if(isFieldOperator(operator)) {
				criteria = new FieldCriteria();
				((FieldCriteria)criteria).setOtherFieldName(jsonObject.getString("value"));
			}
			else {
				criteria = new ValueCriteria();				
				((ValueCriteria)criteria).setValue(jsonObject.get("value"));
			}
		}
		else if(jsonObject.containsKey("start")) {
			criteria = new BetweenCriteria();
			((BetweenCriteria)criteria).setStart(jsonObject.get("start"));
			((BetweenCriteria)criteria).setEnd(jsonObject.get("end"));
		}
		else {
			criteria = new NullCriteria();
		}
		criteria.setFieldName((String)jsonObject.get("fieldName"));
		criteria.setOperator(operator);

		return criteria;
	}

	private boolean isFieldOperator(Operator operator) {
		return fieldOperators.contains(operator);
	}

	private Criteria parseAdvancedCriteria(JSONObject jsonObject) {
		AdvanceCriteria criteria = new AdvanceCriteria();
		
		criteria.setOperator(getOperator(jsonObject));
		JSONArray array = jsonObject.getJSONArray("criteria");
		for(int i=0; i<array.size(); ++i) {
			Criteria child = parse(array.getJSONObject(i));
			criteria.addCriteria(child);
		}
		
		return criteria;
	}
	
	private Operator getOperator(JSONObject jsonObject) {
		String op = (String)jsonObject.get("operator");
		return this.valueToOperator.get(op);
	}
}
